package org.modelcc.parser.fence;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.modelcc.AssociativityType;
import org.modelcc.language.syntax.Grammar;
import org.modelcc.language.syntax.Rule;
import org.modelcc.language.syntax.Symbol;
import org.modelcc.language.syntax.InputSymbol;
import org.modelcc.language.syntax.SyntaxConstraints;

/**
 * Fence associativity constraints
 * 
 * @author Luis Quesada (lquesada@modelcc.org), refactored by Fernando Berzal (fberzal@modelcc.org)
 */

public class FenceAssociativityConstraints 
{
	private SyntaxConstraints constraints;
	
    private Map<Object, AssociativityType> indirectAssociativities;

    private Set<Object> isIndirectAssociative;

    private Set<Object> hasAnyAssociativity;

    private Set<Rule> hasAnyAssociativityRule;
	
    private Set<Object> associateds;
	
    
	public FenceAssociativityConstraints (Grammar g, SyntaxConstraints constraints)
	{
		this.constraints = constraints;
		
		indirectAssociativities = new HashMap<Object,AssociativityType>();
		isIndirectAssociative = new HashSet<Object>();
		hasAnyAssociativity = new HashSet<Object>();
		hasAnyAssociativityRule = new HashSet<Rule>();                 
		associateds = new HashSet<Object>();		

        // indirectAssociativities
		
		Map<Object,AssociativityType> associativities = constraints.getAssociativities();

		indirectAssociativities.putAll(associativities);

        for (Object pe: associativities.keySet()) {
    		if (indirectAssociativities.get(pe)!=null) {
    			if (indirectAssociativities.get(pe)!=AssociativityType.UNDEFINED) {
    				isIndirectAssociative.add(pe);
    				indirectAssociativities.put(pe, indirectAssociativities.get(pe));
    			}
    		}
        }
        
        // hasAnyAssociativity
        
        for (Rule r: g.getRules()) {
            int assocs = 0;
            for (int i = 0;i < r.getRight().size();i++) {
            	if (isIndirectAssociative.contains(r.getRight().get(i).getType()))
            		assocs++;
            }            
            if (assocs>=1) {
            	hasAnyAssociativity.add(r.getLeft().getType());
            	hasAnyAssociativityRule.add(r);
            }
        }

	}

	
	// Accessors
	
	
	private boolean hasAssociativity (Object type)
	{
		return hasAnyAssociativity.contains(type);
	}
	
	private boolean hasAssociativity (Rule rule)
	{
		return hasAnyAssociativityRule.contains(rule);
	}
	
	private boolean isAssociative (Object type)
	{
		return isIndirectAssociative.contains(type);
	}
	
    private AssociativityType associativity (InputSymbol ps) 
    {
    	return indirectAssociativities.get(ps.getType());
    }	
    

    
	// "Associated" symbols
	
	public boolean isAssociate(Symbol s1) 
	{
		boolean associate = false;
	
		for (int i=0; i<s1.size(); i++) {
			if (constraints.getAssociativity(s1.getContent(i).getType()) != null) {
				associate = true;
			}
		}
		
		return associate;
	}
	
	
	public boolean isAssociated (Symbol s)
	{
		return associateds.contains(s);
	}
	
	
	public void associate (Symbol s)
	{
		associateds.add(s);
	}

	
    // Associativity constraint
    
	public boolean inhibit (Rule r, Symbol s1) 
	{
		boolean inhibited = false;
		
		boolean recLeft,recRight;		
		boolean aux;

		for (int i = 0;i < s1.size();i++) {
		     
		    if (constraints.getAssociativity(s1.getContent(i).getType()) != null) {
		        recLeft = false;
		        recRight = false;
		        if (i > 0) {
		            if (s1.getRule().equals(s1.getContent(i-1).getRelevantRule()))
		                recLeft = true;
		            if (associateds.contains(s1.getContent(i-1))) {
		                aux = recLeft;
		                recLeft = true;
		                Set<Rule> compc = constraints.getCompositionPrecedences(s1.getContent(i-1).getRelevantRule());
		                if (compc != null) {
		                    if (compc.contains(r)) {
		                        recLeft = aux;
		                    }
		                }
		            }
		        }
		        if (i < s1.size()-1) {
		            if (s1.getRule().equals(s1.getContent(i+1).getRelevantRule()))
		                recRight = true;
		            if (associateds.contains(s1.getContent(i+1))) {
		                aux = recRight;
		                recRight = true;
		                Set<Rule> compc = constraints.getCompositionPrecedences(s1.getContent(i+1).getRelevantRule());
		                if (compc != null) {
		                    if (compc.contains(r)) {
		                        recRight = aux;
		                    }
		                }
		            }
		        }

		        switch (indirectAssociativities.get(s1.getContent(i).getType())) {
		            case LEFT_TO_RIGHT:
		                if (recRight)
		                    inhibited = true;
		                break;
		            case RIGHT_TO_LEFT:
		                if (recLeft)
		                    inhibited = true;
		                break;
		            case NON_ASSOCIATIVE:
		                if (recRight || recLeft)
		                    inhibited = true;
		                break;
		            case UNDEFINED:
		                break;
		        }
		    }
		}
		return inhibited;
	}
	
	
	// Tuples
	
	public void searchTuples(InputSymbol ps, Set<Tuple> ets) 
	{
		Set<Rule> rules;
	
		if (hasAssociativity(ps.getType())) {
        	// Sacar de ets todas las tuplas que usan una regla con elemento asociativo.
        	Set<Tuple> etsas = new HashSet<Tuple>();
        	rules = new HashSet<Rule>();
        	for (Iterator<Tuple> ite = ets.iterator();ite.hasNext();) {
        		Tuple et = ite.next();
        		if (hasAssociativity(et.getRule())) {
        			ite.remove();
        			etsas.add(et);
        			rules.add(et.getRule());
        		}
        	}
        	// Si todas están formadas usando la misma regla:
        	if (rules.size()==1) {
        		Rule rx = rules.iterator().next();
        		// Buscar el número de elemento que es asociativo.
        		int elm = -1;
        		for (int i = 0;i < rx.getRight().size();i++) {
        			if (isAssociative(rx.getRight().get(i).getType()))
                        elm = i;
                }
                if (elm != -1) {
                    int min = -1;
                    int max = -1;
                    AssociativityType ac = null;
                    boolean ok = true;
                    for (Tuple et: etsas) {
                        InputSymbol asoc = et.getSymbol(elm);
                        AssociativityType acn = associativity(asoc);
                        if (ac != null && acn != ac)
                            ok = false;
                        ac = acn;
                        if (asoc.getStartIndex()<min || min==-1)
                            min = asoc.getStartIndex();
                        if (asoc.getEndIndex()>max)
                            max = asoc.getEndIndex();
                    }
                    
                    if (ac != null && ok && etsas.size()>1) {
                        // Localizar el elemento asociativo en todas, + localizar máximo (=end maximo) y mínimo (=start mínimo).
                        // Si la asociatividad del elemento asociativo es IGUAL en todas:

                        // Si NON_ASSOCIATIVE y hay más de una, borrar todas.
                        if (ac == AssociativityType.NON_ASSOCIATIVE) {
                            etsas.clear();
                        }

                        // Si LEFT_TO_RIGHT: Eliminar todas cuyo elemento con asociatividad.end != end maximo                       
                        if (ac == AssociativityType.LEFT_TO_RIGHT) {
                            for (Iterator<Tuple> ite = etsas.iterator();ite.hasNext();) {
                                Tuple et = ite.next();
                                if (et.getSymbol(elm).getEndIndex()!=max) {
                                    ite.remove();
                                }
                            }
                        }

                        // Si RIGHT_TO_LEFT: Eliminar todas cuyo elemento con asociatividad.start != start minimo
                        if (ac == AssociativityType.RIGHT_TO_LEFT) {
                            for (Iterator<Tuple> ite = etsas.iterator();ite.hasNext();) {
                                Tuple et = ite.next();
                                if (et.getSymbol(elm).getStartIndex()!=min) {
                                    ite.remove();
                                }
                            }
                        }

                    }
                }
                
            }
            // En cualquier caso: devolver a ets.
            ets.addAll(etsas);
        }
	}
	
}
